package com.xuecheng.content.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import com.xuecheng.api.content.model.CourseBaseDTO;
import com.xuecheng.api.content.teachplan.dto.TeachplanDTO;
import com.xuecheng.api.content.teachplan.dto.TeachplanMediaDTO;
import com.xuecheng.api.content.teachplan.vo.TeachplanVO;
import com.xuecheng.api.media.dto.MediaDTO;
import com.xuecheng.api.media.dto.TeachplanlMediaDTO;
import com.xuecheng.common.domain.code.CommonErrorCode;
import com.xuecheng.common.domain.code.ErrorCode;
import com.xuecheng.common.domain.response.RestResponse;
import com.xuecheng.common.enums.common.AuditEnum;
import com.xuecheng.common.enums.common.CommonEnum;
import com.xuecheng.common.enums.content.CourseAuditEnum;
import com.xuecheng.common.enums.content.CourseModeEnum;
import com.xuecheng.common.enums.content.TeachPlanEnum;
import com.xuecheng.common.exception.ExceptionCast;
import com.xuecheng.common.util.StringUtil;
import com.xuecheng.content.agent.MediaApiAgent;
import com.xuecheng.content.common.constant.ContentErrorCode;
import com.xuecheng.content.convert.CourseBaseConvert;
import com.xuecheng.content.convert.TeachplanConvert;
import com.xuecheng.content.convert.TeachplanMediaConvert;
import com.xuecheng.content.entity.CourseBase;
import com.xuecheng.content.entity.Teachplan;
import com.xuecheng.content.entity.TeachplanMedia;
import com.xuecheng.content.entity.ext.TeachplanNode;
import com.xuecheng.content.mapper.TeachplanMapper;
import com.xuecheng.content.service.CourseBaseService;
import com.xuecheng.content.service.TeachplanMediaService;
import com.xuecheng.content.service.TeachplanService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

import java.beans.Expression;
import java.util.ArrayList;
import java.util.List;

/**
 * <p>
 * 课程计划 服务实现类
 * </p>
 *
 * @author xuan
 */
@Slf4j
@Service
public class TeachplanServiceImpl extends ServiceImpl<TeachplanMapper, Teachplan> implements TeachplanService {

    @Autowired
    private CourseBaseService courseBaseService;

    @Autowired
    private MediaApiAgent mediaApiAgent;

    @Autowired
    private TeachplanMediaService teachplanMediaService;

    /**
     * 业务分析
     * 1.判断关键数据
     * 2.判断业务数据
     * 判断是否存在
     * 判断是否被删除
     * 判断是否是同一家机构
     * 3.获得课程下的所有课程计划数据
     * 4.通过java的递归来生成课程计划树形结构
     * 5.将node数据转为dto并返回
     *
     * @param courseId  课程基本信息id
     * @param companyId 公司id
     * @return
     */
    public TeachplanDTO queryTreeNodesByCourseId(Long courseId, Long companyId) {
        // 1.判断关键数据
        if (ObjectUtils.isEmpty(courseId) || ObjectUtils.isEmpty(companyId)) {
            ExceptionCast.cast(ContentErrorCode.E_120023);
        }

        //2.判断业务数据
        //    判断是否存在
        //    判断是否被删除
        //    判断是否是同一家机构
        //courseBaseService.getByCourseId(courseId,companyId);
        LambdaQueryWrapper<CourseBase> baseQueryWrapper = new LambdaQueryWrapper<>();
        baseQueryWrapper.eq(CourseBase::getId, courseId);
        baseQueryWrapper.eq(CourseBase::getCompanyId, companyId);

        CourseBase courseBase = courseBaseService.getOne(baseQueryWrapper);

        if (ObjectUtils.isEmpty(courseBase)) {
            ExceptionCast.cast(ContentErrorCode.E_120013);
        }

        Integer status = courseBase.getStatus();
        if (!(CommonEnum.USING_FLAG.getCodeInt().equals(status))) {
            ExceptionCast.cast(ContentErrorCode.E_120021);
        }
        // 3.获得课程下的所有课程计划数据
        TeachplanMapper baseMapper = this.getBaseMapper();
        List<TeachplanNode> nodes = baseMapper.selectTreeNodesByCourseId(courseId);

        //4.通过java的递归来生成课程计划树形结构
        TeachplanDTO teachplanDTO = null;
        if (CollectionUtils.isEmpty(nodes)) {
            teachplanDTO = new TeachplanDTO();
        } else {

            TeachplanNode rootNode = nodes.remove(0);

            generateTreeNodes(rootNode, nodes);

            teachplanDTO = TeachplanConvert.INSTANCE.node2dto(rootNode);
        }


        //  5.将数据转为dto并返回
        return teachplanDTO;
    }


    private void generateTreeNodes(TeachplanNode parentNode, List<TeachplanNode> nodes) {

        // 1.判断父级集合是否为空，如果为空需要创建
        if (CollectionUtils.isEmpty(parentNode.getChildrenNodes())) {
            parentNode.setChildrenNodes(new ArrayList<>(8));
        }

        // 2.循环遍历集合生成树形结构
        for (TeachplanNode node : nodes) {

            // 2.1 判断node是否和parentnode有关联，如果有关联将node存放到parentNode集合中
            if (ObjectUtils.nullSafeEquals(parentNode.getId(), node.getParentid())) {

                parentNode.getChildrenNodes().add(node);

                // 2.2 生成node集合数据
                // 如果是第三级课程计划，无需递归
                if (!(TeachPlanEnum.THIRD_LEVEL.equals(node.getGrade()))) {
                    generateTreeNodes(node, nodes);
                }
            }
        }

    }

    /**
     * 增加和修改课程计划信息
     * 主体业务方法的分析：
     * 1.判断关键数据
     * pname
     * courseid
     * 2.判断业务操作
     * 判断是否有teachplanid
     * 如果没：添加
     * 有：修改
     * 3.返回teachplandto
     *
     * @param teachplanDTO
     * @param companyId
     * @return
     */
    @Transactional
    public TeachplanDTO createOrModifyTeachPlan(TeachplanDTO teachplanDTO, Long companyId) {
        //1.判断关键数据
        //    pname
        //    courseid
        if (StringUtil.isBlank(teachplanDTO.getPname()) ||
                ObjectUtils.isEmpty(teachplanDTO.getCourseId())
        ) {
            ExceptionCast.cast(CommonErrorCode.E_100101);
        }
        //2.判断业务操作
        //      判断是否有teachplanid
        //          如果没：添加
        //          有：修改
        TeachplanDTO resultDTO = null;
        if (ObjectUtils.isEmpty(teachplanDTO.getTeachPlanId())) {
            resultDTO = createTeachplan(teachplanDTO, companyId);
        } else {
            resultDTO = modifyTeachplan(teachplanDTO, companyId);
        }

        //3.返回teachplandto
        return resultDTO;
    }


    /**
     * 修改
     * 业务分析
     * 1.判断关键数据
     * 2.判断业务数据
     * 课程基础信息
     * 判断是否存在
     * 判断是否同一家机构
     * 判断是否删除
     * 判断课程基础信息审核状态：未提交和审核未通过
     * 课程计划
     * 判断是否存在
     * 3.修改课程计划数据
     * pname starttime endtime is_preview
     * 4.判断修改后的结果
     * 5.将数据库最新的数据转为dto并返回
     *
     * @param teachplanDTO
     * @param companyId
     * @return
     */
    private TeachplanDTO modifyTeachplan(TeachplanDTO teachplanDTO, Long companyId) {
        //1.判断关键数据

        //2.判断业务数据
        //    课程基础信息
        //        判断是否存在
        //        判断是否同一家机构
        //        判断是否删除
        //        判断课程基础信息审核状态：未提交和审核未通过
        //    课程计划
        //        判断是否存在
        verifyTechplanMsg(teachplanDTO, companyId);
        LambdaQueryWrapper<Teachplan> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Teachplan::getId, teachplanDTO.getTeachPlanId());
        int count = this.count(wrapper);
        if (count < 1) {
            ExceptionCast.cast(ContentErrorCode.E_120402);
        }
        //3.修改课程计划数据
        //     pname starttime endtime is_preview
        LambdaUpdateWrapper<Teachplan> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.set(Teachplan::getPname, teachplanDTO.getPname())
                .set(Teachplan::getStartTime, teachplanDTO.getStartTime())
                .set(Teachplan::getEndTime, teachplanDTO.getEndTime())
                .set(Teachplan::getIsPreview, teachplanDTO.getIsPreview())
                .eq(Teachplan::getId, teachplanDTO.getTeachPlanId());

        boolean po = this.update(updateWrapper);
        //4.判断修改后的结果
        if (!po) {
            ExceptionCast.cast(ContentErrorCode.E_120407);
        }

        //5.将数据库最新的数据转为dto并返回
        Teachplan teachplan = this.getById(teachplanDTO.getTeachPlanId());
        TeachplanDTO dto = TeachplanConvert.INSTANCE.entity2dto(teachplan);
        return dto;
    }

    /**
     * 添加
     * 业务分析
     * 1.判断关键数据
     * 2.判断业务数据
     * 课程基础信息
     * 判断是否存在
     * 判断是否同一家机构
     * 判断是否删除
     * 判断课程基础信息审核状态：未提交和审核未通过
     * 3.获得父级数据并进行关联
     * parentid
     * grade：子级grade是父级grade+1
     * orderby：获得父级课程计划的子级数据个数+1（bug）
     * 4.将dto转为po并保存
     * 5.判断保存后的结果
     *
     * @param teachplanDTO
     * @param companyId
     * @return
     */
    private TeachplanDTO createTeachplan(TeachplanDTO teachplanDTO, Long companyId) {
        //1.判断业务数据
        //   课程基本信息
        CourseBaseDTO courseBase = verifyTechplanMsg(teachplanDTO, companyId);
        //2.给添加课程计划关联父级数据
        //    获得父级数据
        //3.添加teachplan其他的数据  父级等级是子级加一
        //    grade
        //    orderby

        Teachplan teachplan = TeachplanConvert.INSTANCE.dto2entity(teachplanDTO);
        Teachplan parentNode = generateParenNode(teachplanDTO, courseBase);
        teachplan.setParentid(parentNode.getId());
        teachplan.setGrade(parentNode.getGrade() + 1);


        //     orderby-->
        //       根据父级id值查找子级数据的个数（bug）: 子级的parentid = 父级的id值
        //  select count(1) from teachplan where parentid = ?
        // LambdaQueryWrapper<Teachplan> queryWrapper = new LambdaQueryWrapper<>();
        LambdaQueryWrapper<Teachplan> queryWrapper = Wrappers.lambdaQuery(Teachplan.class);
        queryWrapper.eq(Teachplan::getParentid, parentNode.getId());
        int count = this.count(queryWrapper);

        // 4.将dto转为po并保存
        teachplan.setOrderby(count + 1);
        boolean result = this.save(teachplan);
        //判断保存后的数据
        if (!result) {
            ExceptionCast.cast(ContentErrorCode.E_120407);
        }

        // 6.将数据库最新数据转为dto并返回
        Teachplan po = getById(teachplan.getId());
        TeachplanDTO resultDTO = TeachplanConvert.INSTANCE.entity2dto(po);


        return resultDTO;
    }

    /**
     * 业务分析：
     * 1.判断新增数据是二级还是三级课程计划
     * 判断新增数据是否有parentId值
     * 如果有：三级课程计划
     * 如果没有：二级课程计划
     * 2.获得二级课程计划的父级数据
     * 查询数据库获得一级课程计划：courseId、parentid
     * 如果没有：自动创建并保存数据--数据来源 cou
     * 如果有：直接返回
     * 3.获得三级课程计划的父级数据
     * 查询数据库获得二级课程计划：parentId
     * 如果没有：直接抛出异常
     * 如果有：直接返回
     *
     * @param teachplanDTO
     * @param courseBase
     * @return
     */
    private Teachplan generateParenNode(TeachplanDTO teachplanDTO, CourseBaseDTO courseBase) {
        //判断新增数据是二级还是三级课程计划
        //  判断新增数据是否有parentId值
        //      如果有：三级课程计划
        //      如果没有：二级课程计划
        Long parentid = teachplanDTO.getParentid();
        if (ObjectUtils.isEmpty(parentid)) {
            //2.获得二级课程计划的父级数据
            //    查询数据库获得一级课程计划：course
            //        如果没有：自动创建并保存数据--
            //        如果有：直接返回
            LambdaQueryWrapper<Teachplan> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(Teachplan::getCourseId, courseBase.getCourseBaseId())
                    .eq(Teachplan::getParentid, TeachPlanEnum.FIRST_PARENTID_FLAG);
            Teachplan rootParent = this.getOne(queryWrapper);

            if (ObjectUtils.isEmpty(rootParent)) {
                rootParent = new Teachplan();
                rootParent.setPname(courseBase.getName());
                rootParent.setParentid(new Long(TeachPlanEnum.FIRST_PARENTID_FLAG));
                rootParent.setGrade(TeachPlanEnum.FIRST_LEVEL);
                rootParent.setDescription(courseBase.getDescription());
                rootParent.setOrderby(TeachPlanEnum.FIRST_LEVEL);
                rootParent.setCourseId(courseBase.getCourseBaseId());
                boolean save = this.save(rootParent);
                if (!save) {
                    ExceptionCast.cast(ContentErrorCode.E_120415);
                }

            }
            return rootParent;
        } else {
            //获得三级课程计划的父级数据
            //  查询数据库获得二级课程计划：parentId
            //      如果没有：直接抛出异常
            //      如果有：直接返回
            LambdaQueryWrapper<Teachplan> Wrapper = new LambdaQueryWrapper<>();
            Wrapper.eq(Teachplan::getId, parentid);
            Teachplan teachplan = this.getOne(Wrapper);
            if (ObjectUtils.isEmpty(teachplan)) {
                ExceptionCast.cast(ContentErrorCode.E_120408);
            }
            return teachplan;

        }

    }

    //判断业务数据
    private CourseBaseDTO verifyTechplanMsg(TeachplanDTO teachplanDTO, Long companyId) {
        //1.判断关键数据
        if (ObjectUtils.isEmpty(companyId) ||
                StringUtil.isBlank(teachplanDTO.getPname()) ||
                ObjectUtils.isEmpty(teachplanDTO.getCourseId())
        ) {
            ExceptionCast.cast(CommonErrorCode.E_100101);
        }


        // 2.判断业务数据
        //     课程基础信息
        //         判断是否存在
        //         判断是否同一家机构
        //         判断是否删除
        //         判断课程基础信息审核状态：未提交和审核未通过
        CourseBaseDTO courseBase = courseBaseService.getByCourseId(teachplanDTO.getCourseId(), companyId);


        String auditStatus = courseBase.getAuditStatus();
        if (CourseAuditEnum.AUDIT_PASTED_STATUS.getCode().equals(auditStatus) ||
                CourseAuditEnum.AUDIT_COMMIT_STATUS.getCode().equals(auditStatus) ||
                CourseAuditEnum.AUDIT_PUBLISHED_STATUS.getCode().equals(auditStatus)
        ) {
            ExceptionCast.cast(ContentErrorCode.E_120015);
        }
        return courseBase;

    }

    /**
     * 删除
     * 业务操作：
     * 1.判断关键
     * teachplanId  companyId
     * 2.判断业务数据
     * 课程基础信息
     * 判断是否存在、判断是否是同一家教学机构、判断是否删除、判断审核状态（未提交、审核未通过）
     * 课程计划
     * 判断删除的课程计划是几级数据
     * 二级
     * 判断是否存在子级数据，如果有不让删除
     * 三级
     * 如果是录播课程
     * 判断课程计划是否有关联数据（查询TeachplanMedia表数据）
     *
     * @param teachPlanId Long 课程计划id
     * @param companyId   Long   公司id
     */
    @Transactional
    public void deleteById(Long teachPlanId, Long companyId) {
        //1.判断关键
        //	teachplanId  companyId


        //2.判断业务数据
        //	课程基础信息
        //		判断是否存在、判断是否是同一家教学机构、判断是否删除、判断审核状态（未提交、审核未通过）
        //	课程计划
        //		判断删除的课程计划是几级数据
        //			二级
        //				判断是否存在子级数据，如果有不让删除
        //			三级
        //				如果是录播课程
        //					判断课程计划是否有关联数据（查询TeachplanMedia表数据）
    }

    /**绑定媒资信息
     * 业务分析：
     *   1.判断关键数据
     *       teachplanid mediaid
     *   2.判断业务数据
     *       课程计划数据
     *           判断是否存在
     *           判断课程计划是否是第三级
     *           判断是否是同一家机构的操作
     *               根据课程基础信息来查询
     *               判断课程类型是否是点播
     *       媒资数据
     *           判断是否存在
     *           判断是否是同一家机构
     *           判断媒资信息是否审核通过
     *   3.保存课程计划和媒资信息关联关系
     *       判断关联数据是否存在
     *           如果不存在
     *               创建数据
     *           如果存在
     *               修改数据
     *                   mediaid 和 medianame
     * @param dto  基本数据
     * @param companyId 公司id
     * @return
     */
    @Transactional
    public TeachplanlMediaDTO associateMedia(TeachplanlMediaDTO dto, Long companyId) {
        //1.判断关键数据
        //    teachplanid mediaid
        Long teachplanId = dto.getTeachplanId();
        if (ObjectUtils.isEmpty(teachplanId)
            ||ObjectUtils.isEmpty(companyId)
        ) {
                ExceptionCast.cast(CommonErrorCode.E_100101);
        }

        //2.判断业务数据
        //    课程计划数据
        //        判断是否存在
        //        判断课程计划是否是第三级
        LambdaQueryWrapper<Teachplan> teachplanLambdaQueryWrapper = new LambdaQueryWrapper<>();
        teachplanLambdaQueryWrapper.eq(Teachplan::getId,dto.getTeachplanId())
                .eq(Teachplan::getGrade,TeachPlanEnum.THIRD_LEVEL);
        Teachplan teachplan = this.getOne(teachplanLambdaQueryWrapper);
        if (ObjectUtils.isEmpty(teachplan)) {
            ExceptionCast.cast(ContentErrorCode.E_120402);
        }

        //        判断是否是同一家机构的操作
        //            根据课程基础信息来查询
        //            判断课程类型是否是点播
        LambdaQueryWrapper<CourseBase> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(CourseBase::getCompanyId,companyId)
                .eq(CourseBase::getId,teachplan.getCourseId())
                .eq(CourseBase::getTeachmode, CourseModeEnum.COURSE_MODE_RECORD_STATUS.getCode())
                .eq(CourseBase::getStatus,CommonEnum.USING_FLAG.getCodeInt());
        CourseBase courseBase = this.courseBaseService.getOne(queryWrapper);

        // 课程计划和媒资信息绑定要求课程审核状态为：未提交和审核未通过
        String auditStatus = courseBase.getAuditStatus();
        if (CourseAuditEnum.AUDIT_PASTED_STATUS.getCode().equals(auditStatus)||
                CourseAuditEnum.AUDIT_COMMIT_STATUS.getCode().equals(auditStatus)||
                CourseAuditEnum.AUDIT_PUBLISHED_STATUS.getCode().equals(auditStatus)
        ) {
            ExceptionCast.cast(ContentErrorCode.E_120015);
        }

        //    媒资数据
        //        判断是否存在
        //        判断是否是同一家机构
        //        判断媒资信息是否审核通过
        RestResponse<MediaDTO> restResponse = mediaApiAgent.getById(dto.getMediaId());
        if (!(restResponse.isSuccessful())) {
            //RestResponse 错误信息
            ExceptionCast.castWithCodeAndDesc(restResponse.getCode(),restResponse.getMsg());
        }
        MediaDTO mediaDTO = restResponse.getResult();
        if (!(ObjectUtils.nullSafeEquals(mediaDTO.getCompanyId(),companyId))) {
            ExceptionCast.cast(CommonErrorCode.E_100108);

        }
        if (!(AuditEnum.AUDIT_PASTED_STATUS.getCode().equals(mediaDTO.getAuditStatus()))) {
            ExceptionCast.cast(ContentErrorCode.E_120417);
        }
        //  3.保存课程计划和媒资信息关联关系
        //      判断关联数据是否存在
        LambdaQueryWrapper<TeachplanMedia> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(TeachplanMedia::getTeachplanId,teachplan.getId())
                .eq(TeachplanMedia::getCourseId,teachplan.getCourseId());
        int count = this.teachplanMediaService.count(wrapper);
        boolean result = false;
        if (count < 1) {
            //          如果不存在
            //              创建数据
            TeachplanMedia teachplanMedia = new TeachplanMedia();
            teachplanMedia.setMediaId(mediaDTO.getId());
            teachplanMedia.setTeachplanId(teachplan.getId());
            teachplanMedia.setCourseId(teachplan.getCourseId());
            teachplanMedia.setMediaFilename(mediaDTO.getFilename());

            result = teachplanMediaService.save(teachplanMedia);


        } else {
            //          如果存在
            //              修改数据
            //                  mediaid 和 medianame
            LambdaUpdateWrapper<TeachplanMedia> updateWrapper = new LambdaUpdateWrapper<>();
            updateWrapper.set(TeachplanMedia::getMediaId, mediaDTO.getId());
            updateWrapper.set(TeachplanMedia::getMediaFilename, mediaDTO.getFilename());
            updateWrapper.eq(TeachplanMedia::getTeachplanId, teachplan.getId());

            result = teachplanMediaService.update(updateWrapper);
        }

        if (!result) {
            ExceptionCast.cast(ContentErrorCode.E_120411);
        }
        //  4.将数据封装成dto并返回
        LambdaQueryWrapper<TeachplanMedia> teachplanMediaWrapper = new LambdaQueryWrapper<>();
        teachplanMediaWrapper.eq(TeachplanMedia::getTeachplanId, teachplan.getId());

        TeachplanMedia teachplanMedia = teachplanMediaService.getOne(teachplanMediaWrapper);

        TeachplanlMediaDTO teachplanlMediaDTO = TeachplanMediaConvert.INSTANCE.entity2dto(teachplanMedia);



        return teachplanlMediaDTO;
    }


}

